package com.easyim.imserver.handler.cmd;

import com.easyim.common.message.Immessage;
import com.easyim.imserver.handler.message.Handler;
import com.easyim.imserver.service.ITokenService;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static com.easyim.imserver.constant.CacheKey.*;

@Component
@Slf4j
public class ConnectHandler implements Handler {

    @Autowired
    private ITokenService tokenService;

    @Autowired
    @Qualifier("channelMap")
    Map<String, Map<String, Channel>>  localSession;

    @Value("${com.easyim.run.model.clusterable}")
    private Boolean cluster;

    @Override
    public String dealType() {
        return Immessage.CmdMessage.CommandType.CONNECT.name();
    }

    /**
     * 对于新的连接请求
     * @param channel
     * @param imMessage
     */
    @Override
    public void handlerMsg(Channel channel, Immessage.ImMessage imMessage) {
        //检查命令消息
        Immessage.ResponseMessage.Builder builder = Immessage.ResponseMessage.newBuilder();
        Immessage.CmdMessage cmdMessage = imMessage.getCmdMessage();
        //校验token
        log.info("user:{} use tcp connect.. start auth!", cmdMessage.getUsername());
        //todo 做认证逻辑 这种认证 只需要实现某个类的接口即可 这里其实可以直接设置个http的客户端 直接调用
        Boolean authSuccess = tokenService.checkToken(null);
        // 如果认证成功了 就直接连
        if (authSuccess) {
            //一个端只能登陆一个账号 如果当前端判断已经有了会话。那么就要发送踢下去命令
            //把会话加入缓存 加入到redis里面
            String device = cmdMessage.getDevice();
            String username = cmdMessage.getUsername();
            channel.attr(USERNAME).set(username);
            channel.attr(DEVICE).set(device);
            channel.attr(AUTHENTICATED).set(Boolean.TRUE);
            channel.attr(ACTIVE_TIME).set( System.currentTimeMillis());
            //channelinfo放进去redis如果是本机的 那么自己投递 如果不是的话 那么就找其他节点投递
            //把会话 放进缓存中
            if(cluster){
                //集群模式存储会话redis 否则直接存在本地里面就可以了
            }
            //把当前会话缓存 到时候用来发消息
            if(localSession.containsKey(username)){
                //说明其他端可能在这个设备有登陆了
                Map<String, Channel> devicecChannelMap = localSession.get(username);
                if(devicecChannelMap.containsKey(device)){
                    //todo 这个设备 在当前端 登陆了 所以发个cmd 告诉之前旧的channel被人踢了
                    //集群版这个要检查redis的
                }
                devicecChannelMap.put(device, channel);
            }else {
                //先把会话列表推给客户端，然后把所有消息给客户端。这样子 才会稳妥。直到没有消息了 然后才认为他连接成功了
                //给会话的时候 要把最后一条消息id也给了。还有未读条数.. 只有用户去拉取未读消息。查最接近的那十个消息。查到了给客户端就完事了
                //客户端的会话列表 需要加锁 锁住 然后就算有新消息到了 也只是拉完会话列表 进行一个重排序

                //这里给会话的原因只有一个告诉客户端 你还有多少会话 多少会话里面有消息 你需要拉取 你不拉取是客户端的事。查询客户上一次读取到哪里了。把分数大于上次读取到的会话全取出来。
                //本地数据库。最新的消息id拉取会话列表. 这个前端来传需要把自己的消息id传过来. 第一时间。在登陆成功后 立马拉取会话列表。把自己数据库的消息id传过来 然后再建立长链接 如果不传的话 就查询所有会话
                //之前设计的服务器端保存每个端读取到哪里了 方案的不合适之处在于 刚登陆 有很多未读消息 但是有一条新消息下发 游标会进行大幅度的跳跃。导致消息失灵了
                Map<String, Channel> devicecChannelMap = new ConcurrentHashMap<>();
                devicecChannelMap.put(device, channel);
                localSession.put(username, devicecChannelMap);
            }
            builder.setStatus(Immessage.ResponseMessage.Code.SUCCESS);
        } else {
            //todo 发送消息告诉客户端鉴权失败
            builder.setStatus(Immessage.ResponseMessage.Code.AUTH_FAIL);
            builder.setFailReason("fail to auth token! please update token!");
        }
    }
}
